package com.xfcy.blog.aspect;


import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xfcy.blog.annotation.BusinessLogger;
import com.xfcy.blog.common.R;
import com.xfcy.blog.common.constant.Constant;
import com.xfcy.blog.entity.ExceptionLog;
import com.xfcy.blog.entity.UserLog;
import com.xfcy.blog.mapper.ExceptionLogMapper;
import com.xfcy.blog.mapper.UserLogMapper;
import com.xfcy.blog.utils.AspectUtils;
import com.xfcy.blog.utils.IpUtils;
import com.xfcy.blog.vo.SystemUserVO;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Objects;

/**
 * @author 晓风残月Lx
 * @date 2023/5/28 11:38
 */
@Aspect
@Component
@RequiredArgsConstructor
public class BusinessLoggerAspect {

    private static final Logger logger = LoggerFactory.getLogger(BusinessLoggerAspect.class);

    private final UserLogMapper userLogMapper;

    private final ExceptionLogMapper exceptionLogMapper;

    /**
     * 设置操作日志切入点 在注解的位置切入代码
     *
     * @param businessLogger
     */
    // execution(public * com.xfcy.blog.controller.*.*(..)) 表示所有controller
    @Pointcut(value = "@annotation(businessLogger)")
    public void pointAspect(BusinessLogger businessLogger) {
    }

    /**
     * Around注解可以用来在调用一个具体方法前和调用后来完成一些具体的任务。
     *
     * @param joinPoint      方法的执行点
     * @param businessLogger 方法返回值
     * @return
     */
    @Around(value = "pointAspect(businessLogger)")
    public Object doAround(ProceedingJoinPoint joinPoint, BusinessLogger businessLogger) throws Throwable {
        System.out.println("--------Around方法开始执行");
        //先执行业务
        Object result = joinPoint.proceed();

        try {
            // 日志收集
            handle(joinPoint, (R) result);
        } catch (Exception e) {
            logger.error("日志记录出错！", e);
        }


        return result;
    }

    @AfterThrowing(value = "pointAspect(businessLogger)", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, BusinessLogger businessLogger, Throwable e) {
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从requestAttributes中获取HttpServletRequest的信息
        assert requestAttributes != null;
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        String ip = IpUtils.getIp(request);

        String paramsJson = getParamsJson((ProceedingJoinPoint) joinPoint);
        String operationName = AspectUtils.INSTANCE.parseParams(joinPoint.getArgs(), businessLogger.value());



        ExceptionLog exceptionLog = ExceptionLog.builder().ip(ip)
                .ipSource(IpUtils.getIp2region(ip)).params(paramsJson)
                .method(joinPoint.getSignature().getName())
                .username("游客")
                .operation(operationName).exceptionJson(JSON.toJSONString(e))
                .exceptionMessage(e.getMessage()).createTime(LocalDateTime.now()).build();

        exceptionLogMapper.insert(exceptionLog);
    }

    /**
     * 记录操作日志
     *
     * @param joinPoint 方法的执行点
     * @param result    方法返回值
     */
    private void handle(ProceedingJoinPoint joinPoint, R result) {
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从requestAttributes中获取HttpServletRequest的信息
        assert requestAttributes != null;
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);

        // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取切入点所在的方法
        Method method = signature.getMethod();
        // 获取操作
        BusinessLogger annotation = method.getAnnotation(BusinessLogger.class);
        if (!annotation.save()) {
            return;
        }

        assert request != null;
        String ip = IpUtils.getIp(request);
        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("user-agent"));
        String clientType = userAgent.getOperatingSystem().getDeviceType().toString();
        String os = userAgent.getOperatingSystem().getName();
        String browser = userAgent.getBrowser().toString();

        UserLog userLog = UserLog.builder().model(annotation.value()).type(annotation.type())
                .description(annotation.desc()).ip(ip).address(IpUtils.getIp2region(ip))
                .clientType(clientType).accessOs(os).browser(browser).result(result.getMsg()).
                createTime(LocalDateTime.now()).build();
        if (StpUtil.isLogin()) {
            userLog.setUserId(StpUtil.getLoginIdAsLong());
        }
        userLogMapper.insert(userLog);
    }


    private String getParamsJson(ProceedingJoinPoint joinPoint) {
        // 参数值
        Object[] args = joinPoint.getArgs();
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        String[] parameterNames = methodSignature.getParameterNames();

        // 通过map封装参数和参数值
        HashMap<String, Object> paramMap = new HashMap<>();
        for (int i = 0; i < parameterNames.length; i++) {
            paramMap.put(parameterNames[i], args[i]);
        }

        boolean isContains = paramMap.containsKey("request");
        if (isContains) paramMap.remove("request");
        return JSONObject.toJSONString(paramMap);
    }
}
